﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;

using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Domain;

using Fuse8.ContentManagementFramework.Domain;
using Fuse8.ContentManagementFramework.SystemFacade.Workflow;
using Fuse8.ContentManagementFramework.SystemFacade.Exceptions;

namespace Fuse8.ContentManagementFramework.BizFacade.Workflow
{    
    public class CmsWorkflowFacade<TEntity, TKey> : IDisposable
        where TEntity : class, IWorkflowEntityObject<TKey>
        where TKey : IComparable<TKey>, IEquatable<TKey>
    {
        #region Fields

        private static ManualResetEvent _gearBox = new ManualResetEvent(false);
        private static int _countOfExecuting = 0;

        private int _waitSyncTime = 30 * 1000; //30 sec
        private ICmsWorkflowStateMachine<TEntity, TKey> _workflowMachine;
        private ICmsWorkflowValidationService<TEntity, TKey> _workflowValidationService;

        private TEntity _entity;

        #endregion

        #region Properties

        /// <summary>
        /// In ms
        /// </summary>
        public int WaitSyncTime
        {
            get
            {
                return _waitSyncTime;
            }
            set
            {
                _waitSyncTime = value;
            }
        }

        public CmsWorkflowState CurrentState
        {
            get
            {
                return _workflowMachine.CurrentWorkflowState;
            }
        }

        #endregion

        #region Constructors

        public CmsWorkflowFacade(TEntity entity)
        {
            if (_countOfExecuting > 0)
            {
                _gearBox.WaitOne(WaitSyncTime);
            }

            //Defaults
            if (!DependencyServiceLocator.Current.IsDefaultServiceRegistered<ICmsWorkflowStateMachine<TEntity, TKey>>())
            {
                DependencyServiceLocator.Current.SetDefaultService<ICmsWorkflowStateMachine<TEntity, TKey>>(
                    new BaseApprovalWorkflowService<TEntity, TKey>());
            }

            if (!DependencyServiceLocator.Current.IsDefaultServiceRegistered<ICmsWorkflowStorageService<TEntity, TKey>>())
            {
                DependencyServiceLocator.Current.SetDefaultService<ICmsWorkflowStorageService<TEntity, TKey>>(
                    new WorkflowStorageService<TEntity, TKey>());
            }

            if (DependencyServiceLocator.Current.IsRegistered<ICmsWorkflowValidationService<TEntity, TKey>>())
            {
                _workflowValidationService = DependencyServiceLocator.Current.GetInstance<ICmsWorkflowValidationService<TEntity, TKey>>();
            }

            _entity = entity;
            _workflowMachine = DependencyServiceLocator.Current.GetInstance<ICmsWorkflowStateMachine<TEntity, TKey>>();
            _workflowMachine.Start(entity);

            _countOfExecuting++;
        }

        #endregion

        #region Methods

        public void Create(DateTime? approvalDeadline, DateTime? reviewDeadline)
        {
            _workflowMachine.Create(approvalDeadline, reviewDeadline);
        }

        public void Withdraw()
        {
            _workflowMachine.Withdraw();
        }

        public Dictionary<int, string> GetAvailableTransitions()
        {
            return _workflowMachine.AvailableTransitions.ToDictionary(
                p => p.TransitionId,
                p => p.TransitionTitle);
        }

        public void MakeTransition(int transitionId, string notes, int? assignedUserId = null)
        {
            var transitionToRun = _workflowMachine.AvailableTransitions.FirstOrDefault(p => p.TransitionId == transitionId);

            if (transitionToRun != null)
            {
                MakeTransition(transitionToRun, notes, assignedUserId);
            }
            else 
            {
                throw new NotSupportedException(); //TO DO
            }
        }

        public void MakeTransition(CmsWorkflowStateTransition transition, string notes, int? assignedUserId = null)
        {
            Guard.ArgumentNotNull(transition, "transition");

            Validate(transition);

            try
            {
                _workflowMachine.MakeTransition(transition, notes, assignedUserId);
            }
            catch (Exception ex)
            {
                throw new WorkflowException(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
            }
        }

        private void Validate(CmsWorkflowStateTransition transition)
        {
            if (_workflowValidationService != null)
            {
                var validationResult = _workflowValidationService.Validate(_entity, transition);
                if (!validationResult.IsValid)
                {
                    throw new ValidationException(validationResult.ValidationMessage);
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            _workflowMachine.Dispose();
            _workflowMachine = null;
            
            _countOfExecuting--;

            _gearBox.Set(); 
        }

        #endregion
    }
}
